「原來 Angular 這麼好玩啊?!」Wayne 做完練習後,開心地跟我說道。
「是不是?!之後如果寫再複雜一點的應用更能體現出 Angular 厲害的地方!」我信心滿滿地說。
「不過完成了這個應用之後,我更好奇 Angular 是怎麼運作的了!」Wayne 好奇地說。
「那就先從目前有實作過的東西來解釋吧!我跟你說...」
Angular 是一個很注重模組化開發的框架,它有著自己特有的模組系統-NgModule。
而每個用 Angular 寫出來的應用程式,至少都會有一個 NgModule 的類別-根模組 (Root Module) 。通常我們會把這個根模組取名為 AppModule,且這個根模組會放在 app.module.ts
這支檔案裡 (其實這些事情,Angular CLI 都幫我們處理好了) 。
雖然一個小一點、簡單一點的應用程式,可能只會有一個 NgModule,但大多數的應用程式都還會有著其他大大小小、各式各樣的功能模組。
拿我們寫過的 MTRStationList 的 AppModule 來簡單說明一下:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
// Component
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
在 Angular 裡,我會將一個檔案大致上切分為三個區塊。
首先是引入的區塊:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
// Componet
import { AppComponent } from './app.component';
要引入 (import) 的程式碼都放在這個區塊 (我個人喜歡分類並加上註解) 。
再來是裝飾器(Decorator)區塊:
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
裝飾器一開始是 Typescript 為了 Angular 先行加入的功能,JavaScript 則是直到 ES7 才確定加入的新功能(關於裝飾器,TypeScript 可以參考官方文件,JavaScript 的部份則可以參考此篇文章)。
傳入裝飾器裡的物件,在 Angular 裡稱作中繼資料 (MetaData) ,用來告訴 Angular 要怎麼樣處理接下來的的類別。
要傳入 @NgModule
裝飾器內的中繼資料大致上會有以下幾個較常用的屬性:
declarations-屬於此 NgModule 的 Component、Directive 與 Pipe 皆放置於此。
imports-此 NgModule 需要使用、依賴的其他 NgModule 皆放置於此(好像有點饒舌)。
providers-可以被整個應用程式中的任何部分被使用的 Service 皆放置於此,也可以將 Service 直接放置在 Component 的 Metadata 裡的 providers
(但放置在不同地方會有一些需要特別注意的事項,後續在說明 Service 時會提到。另外,在 Angular 6 之後,在 Service 之中也可以使用 Metadata 裡的 providedIn
宣告,該 Service 要 provided
到哪裡。詳細可以參考此篇文章或是隔壁棚Angular 大師之路也有提到) 。
exports-此處放置的是,當在其他 NgModule 裡 import 了當前的 Module 後,可以在其他 NgModule 裡的 Component Template 使用的 Component、Directive 與 Pipe。
entryComponents-放在這裡的元件通常是用不通過 Route 的方式,而採用動態加入的元件。
bootstrap-在此設置的是應用程式通常稱之為 Root Component (根元件) ,而且只有 Root Module 才要設置此屬性。
最後則是類別(Class):
export class AppModule { }
每個檔案都會有一個要 export 給別人使用的類別,像上述範例的就是要將 AppModule 這個類別 export 出去讓其他人使用 (Root Module 實際上也只會被 main.ts
引用) 。
隔壁棚也有篇文章在解說 NgModule,點我多學一點!
在 Angular 的世界裡,Component 負責定義與控制畫面,令其可以根據資料和程式邏輯呈現相對應的畫面。
一樣拿我們寫過的 MTRStationList 的 AppComponent 來簡單說明一下:
import { Component } from '@angular/core';
// Constant
import { stationList } from './station-list.const';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
/**
* 所有列車到站站名之資料
*
* @memberof AppComponent
*/
list = stationList;
}
還記得在介紹 NgModules 時,我有提到過我會將一個檔案分成三個區塊嗎?沒錯!這裡也是一樣分成三個區塊。不過這次不重複說明其他兩個區塊,我們將重點擺在裝飾器的部份:
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
有注意到嗎?之前 NgModule 的裝飾器名稱就叫做 @NgModule
,而 Component 的裝飾器名稱就改為叫做 @Component
了!
謎之音:廢話!!(飛踢)
而且傳入 @Component
中的 Metadata 也跟 @NgModule
的不一樣!
謎之音:還是廢話!!(再飛踢)
不過一樣的是,傳入的 Metadata 都是為了告訴 Angular,要怎麼處理接下來的類別。
謎之音:到底哪來這麼多廢話!!(過肩摔)
而要傳入 @Component
裡的 Metadata ,大致會有以下幾個比較常用的屬性(鼻青臉腫地說):
selector-CSS 選擇器,它告訴 Angular 在 Template 中找到相應的位置之後,創建並插入該Component 的實體。以上述的例子來說,Angular 會在 Template 中尋找 <app-root></app-root>
這樣子的標籤,然後創建出 AppComponent 的實體並將其插入其中。
templateUrl-此 Component 的 Template 檔案位置 (相對位置) 。
template-跟 templateUrl
的用途類似,只是 templateUrl
的值是檔案的位置,這裡是直接放 HTML 的語法 (如非必要,不建議使用) 。
styleUrls-光看名字就覺得是跟 templateUrl
類似的東西。沒錯!一樣是檔案位置,只不過換成是樣式檔而已。
styles-跟 styleUrls
與 template
類似,這裡可以直接放 CSS 的語法 (如非必要,不建議使用)。
providers-跟 NgModule 的 providers
類似。但要注意的是,在 NgModule 的 providers
裡宣告的 Service (或是直接在 Service 的 provideIn 宣告為 'root'),整個應用程式的生命週期只會有一個實體 (類似 singleton 的概念) ;但在此處宣告的 Service,實體數量是跟該 Component 的實體數量一致的。
@Component
裡面預設沒有顯示 template、styles和providers,請問會有手動加入的時機嗎?
@NgModule
加入的吧?所以這三個應該都不會有手動加入的時機囉?
Hi SuperMike,
之所以預設都是用 xxxUrl
的方式,就是因為 Google 想要引導大家把檔案切割出來,以便在協作或是後續維護時方便大家處理,不易於產生衝突。
所以如果今天你覺得該 Component 的 template 真的很簡單,或是你不想要將檔案切割出去自成一個 html 檔的話,其實就可以用 template
這個屬性來替代 templateUrl
;而 style 的部份也是同一個道理。
至於 providers
,則是跟 Service 的生命週期有相關,後續我在基礎結構說明(四)的時候會提到。